package com.yn.sample.util;

import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.net.URL;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;

/**
 * 根据传入的class文件，找到对应的file
 * 对其进行javap反编译
 * 反编译的结果，写到class文件所在位置的旁边。
 * 比如：
 * 针对Class：CheckAndSet
 * 其classFile位置：
 * F:\xxx\target\classes\com\yn\method\CheckAndSet.class
 * 最终输出一个javap文件：
 * F:\xxx\target\classes\com\yn\method\CheckAndSet-javap.txt
 */
@Slf4j
public class JavapUtil {
    public static String classPathRootDir = null;

    public static HashMap<Class,String> class2JavapFileHashMap = new HashMap<>();

    public static void findClassPathRootDir() {
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        URL resource = loader.getResource("logback-spring.xml");
        String path = resource.getPath();
        classPathRootDir = path.substring(0, path.length() - "logback-spring.xml".length());
        log.info("classPathRootDir:{}", classPathRootDir);
    }

    public static String invokeJavap(Class<?> clazz) {
        /**
         * 1 先查询缓存
         */
        String javapContent = class2JavapFileHashMap.get(clazz);
        if (javapContent != null) {
            return javapContent;
        }

        if (classPathRootDir == null) {
            findClassPathRootDir();
        }


        try {
            File processOutputFile = executeJavapProcess(clazz);

            RandomAccessFile randomAccessFile = new RandomAccessFile(processOutputFile, "rw");
            FileChannel fileChannel = randomAccessFile.getChannel();
            long length = randomAccessFile.length();
            MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE,
                    0, length);

            /**
             * 读取到字符串中
             */
            StringBuilder stringBuilder = new StringBuilder((int) length);
            byte[] bytes = new byte[(int) length];
            while (mappedByteBuffer.hasRemaining()) {
                int remaining = mappedByteBuffer.remaining();
                if (remaining < bytes.length) {
                    mappedByteBuffer.get(bytes,0,bytes.length);
                } else {
                    mappedByteBuffer.get(bytes,0,remaining);
                }

                stringBuilder.append(new String(bytes,StandardCharsets.UTF_8));
            }

            String result = stringBuilder.toString();
            class2JavapFileHashMap.put(clazz, result);

            return result;
        } catch (IOException e) {
            log.error("e:{}", e);
            throw new RuntimeException(e);
        }

    }

    /**
     * 执行javap，对class文件反编译，结果输出到指定文件中存放
     * @param clazz
     * @return
     * @throws IOException
     */
    private static File executeJavapProcess(Class<?> clazz) throws IOException {
        /**
         * javap输出结果，将重定向到的文件；
         * 即存储javap结果的文件
         */
        String javapFileName;
        /**
         * 原始的class文件，作为javap命令的参数
         */
        String classFileName;
        {
            String name = clazz.getName().replace(".", File.separator);
            javapFileName = name + "-javap.txt";
            classFileName = name + ".class";
        }

        File dir = new File(classPathRootDir);

        ProcessBuilder builder = new ProcessBuilder("javap", "-v", classFileName);
        builder.directory(dir);
        File processOutputFile = new File(classPathRootDir, javapFileName);
        if (!processOutputFile.exists()) {
            processOutputFile.createNewFile();
            processOutputFile.setWritable(true);
            processOutputFile.setReadable(true);
        }
        builder.redirectOutput(processOutputFile);

        log.info("command builder:{}",builder.toString());
        Process process = builder.start();

        log.info("return:{}", getContentFromStream(process.getInputStream(), StandardCharsets.UTF_8));
        log.info("error:{}", getContentFromStream(process.getErrorStream(), Charset.forName("gbk")));
        int exitValue = process.exitValue();
        log.info("status:{}", exitValue);
        if (exitValue != 0) {
            log.error("error:{}", exitValue);
            throw new RuntimeException();
        }

        return processOutputFile;
    }

    public static String getContentFromStream(InputStream inputStream, Charset charset) {
        InputStreamReader inputStreamReader = null;
        inputStreamReader = new InputStreamReader(inputStream, charset);
        char[] buf = new char[2048];
        StringBuilder builder = new StringBuilder();
        int count = -1;
        try {
            while (true) {
                if ((count = inputStreamReader.read(buf, 0, buf.length)) == -1) {
                    break;
                }
                builder.append(buf, 0, count);
            }
        } catch (IOException e) {
            log.error("{}",e);
        } finally {
            if (inputStreamReader != null) {
                try {
                    inputStreamReader.close();
                } catch (IOException e) {
                    //ignore
                }
            }
        }
        String s = builder.toString();

        return s;
    }
}
